package tools

import (
	"archive/zip"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

// Unzip 将 zip 文件解压到目标目录
func Unzip(zipFile, dest string) error {
	reader, err := zip.OpenReader(zipFile)
	if err != nil {
		return err
	}
	defer reader.Close()
	if err := os.MkdirAll(dest, 0755); err != nil {
		return err
	}
	for _, file := range reader.File {
		err := extractFile(file, dest)
		if err != nil {
			return err
		}
	}
	return nil
}

// extractFile 解压单个文件或目录
func extractFile(file *zip.File, dest string) error {
	// 构建目标路径
	filePath := filepath.Join(dest, file.Name)

	// 防止路径遍历攻击（如: ../../../etc/passwd）
	if !isValidPath(filePath, dest) {
		return fmt.Errorf("无效路径，可能路径遍历攻击: %s", filePath)
	}

	// 如果是目录，创建它
	if file.FileInfo().IsDir() {
		return os.MkdirAll(filePath, file.Mode())
	}

	// 确保父目录存在
	if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
		return err
	}

	// 打开压缩包中的文件
	rc, err := file.Open()
	if err != nil {
		return err
	}
	defer rc.Close()

	// 创建目标文件
	dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
	if err != nil {
		return err
	}
	defer dstFile.Close()

	// 复制内容
	_, err = io.Copy(dstFile, rc)
	return err
}

// isValidPath 防止路径遍历（如: ../../ 越界）
func isValidPath(filePath, dest string) bool {
	rel, err := filepath.Rel(dest, filePath)
	if err != nil {
		return false
	}
	return !strings.HasPrefix(rel, "..")
}
